// Copyright 2006 Team AWESOME. All Rights Reserved.
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.BitSet;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class Indexer implements WordParser {

  public static final class OutputException extends RuntimeException {
  }

  public static final int MIN_FILTER_SIZE = 8;

  public Indexer(UserSettings settings, RandomAccessFile outFile) {
    this.settings = settings;
    this.wAnalyzer = settings.getWordAnalyzer();
    this.outFile = outFile;
    this.bitWriter = new BitWriter(outFile);
  }

//  public void debugPrintResults() {
//    // Print corpus results
//    System.out.println(new Date());
//    String varBreakChars = "";
//    CharacterAnalyzer characterAnalyzer = settings.getCharacterAnalyzer();
//    for (int i = CharacterAnalyzer.NONBREAK_BEGIN; i < CharacterAnalyzer.NONBREAK_END; ++i) {
//      if (characterAnalyzer.isBreakChar(i)) {
//        varBreakChars += (char) i;
//      }
//    }
//    System.out.println("Variable break characters=" + varBreakChars);
//
//    String stopFileName = settings.getStopFileName();
//    if (stopFileName != null) {
//      System.out.println("Stop file: "
//          + new File(stopFileName).getAbsolutePath());
//    }
//
//    int corpusFiles = 0, corpusBytes = 0, corpusWords = 0, corpusLines = 0;
//    for (Iterator it = fileInfos.iterator(); it.hasNext();) {
//      FileInfo info = (FileInfo) it.next();
//      ++corpusFiles;
//      corpusBytes += info.getBytes();
//      corpusWords += info.getWords();
//      corpusLines += info.getLines();
//      System.out.println(info.getFilePath() + ": " + info.getBytes()
//          + " bytes, " + info.getWords() + " words, " + info.getLines()
//          + " lines");
//    }
//    System.out.println("Corpus Total: " + corpusBytes + " bytes, "
//        + corpusWords + " words, " + corpusLines + " lines, " + corpusFiles
//        + " files");
//  }

  public void endOfStream(int totalBytes, int totalLines) {
    if (curFileInfo == null) {
      throw new IllegalStateException();
    }

    flushLinesUpTo(totalLines - 1);

    curFileInfo.setBytes(totalBytes);
    curFileInfo.setLines(totalLines);
    fileInfos.addLast(curFileInfo);
    curFileInfo = null;
  }

  public void finish() throws IOException {
    if (curFileInfo != null) {
      throw new IllegalStateException();
    }

    bitWriter.finish();
    long trailerStart = outFile.getFilePointer();

    // 1: print date of time of construction
    outFile.writeLong(new Date().getTime());

    // 2: print variable break characters
    CharacterAnalyzer characterAnalyzer = settings.getCharacterAnalyzer();
    for (int i = CharacterAnalyzer.NONBREAK_BEGIN; i < CharacterAnalyzer.NONBREAK_END; ++i) {
      if (characterAnalyzer.isBreakChar(i)) {
        outFile.writeByte(i);
      }
    }
    outFile.writeByte(0);

    // 3: print stop word file name
    String stopFileName = settings.getStopFileName();
    if (stopFileName != null) {
      outFile.writeUTF(new File(stopFileName).getAbsolutePath());
    } else {
      outFile.writeUTF("");
    }

    // 4: print information about each file
    for (Iterator it = fileInfos.iterator(); it.hasNext();) {
      FileInfo info = (FileInfo) it.next();
      outFile.writeUTF(info.getFilePath());
      outFile.writeInt(info.getBytes());
      outFile.writeInt(info.getWords());
      outFile.writeInt(info.getLines());
    }
    outFile.writeUTF("");

    // 5: corpus stats can be inferred

    // Last four bytes are the size of the trailer
    long finalFileSize = outFile.getFilePointer() + 4;
    outFile.writeInt((int) (finalFileSize - trailerStart));
    outFile.close();
  }

  public void processWord(String word, int lineNumber) {
    if (curFileInfo == null) {
      throw new IllegalStateException();
    }
    curFileInfo.countWord();
    word = Util.toLowerCase(word);
    if (wAnalyzer.isStopWord(word)) {
      return;
    }

    flushLinesUpTo(lineNumber - 1);
    lineInfo.add(word);
  }

  public void startNewFile(String fileName) {
    if (curFileInfo != null) {
      throw new IllegalStateException();
    }
    String absPath = new File(fileName).getAbsolutePath();
    curFileInfo = new FileInfo(absPath);
    curLine = 0;
  }

  private void flushLinesUpTo(int lineNum) {
    if (curLine > lineNum) {
      return;
    }

    try {
      // Flush the current line (may be empty)
      List bloomFilters = lineInfo.generateBloomFilters();
      for (Iterator it = bloomFilters.iterator(); it.hasNext();) {
        BitSet bitSet = (BitSet) it.next();
        // write the size
        int nBits = bitSet.size();
        while (nBits >= MIN_FILTER_SIZE) {
          bitWriter.write(true);
          nBits >>= 1;
        }
        bitWriter.write(false);
        // write the bits
        for (int i = 0; i < bitSet.size(); ++i) {
          bitWriter.write(bitSet.get(i));
        }
      }

      // write a 0 for this line, and every line up to lineNum
      while (curLine <= lineNum) {
        bitWriter.write(false);
        ++curLine;
      }
    } catch (IOException e) {
      throw new OutputException();
    }
  }

  private BitWriter bitWriter;
  private FileInfo curFileInfo = null;
  private int curLine = 0;
  private final LinkedList fileInfos = new LinkedList();
  private final LineInfo lineInfo = new LineInfo();
  private RandomAccessFile outFile;
  private final UserSettings settings;
  private final WordAnalyzer wAnalyzer;
}
